package orm

import (
	"api/libs/errs"
	"api/public/global"
	"context"
	"encoding/json"
	"fmt"
	"gitee.com/dreamwood/ez"
	"gorm.io/gorm"
	"reflect"
	"strings"
)

type Factory struct {
	Ctx context.Context
	Db  *gorm.DB
	//计时器
	Timer *ez.Timer
	//缓存池
	ScanPool *ScanPool
	//事件开关
	IsEventOn bool
	//配置信息
	ModelConfig *EzModelConfig
	//配置组
	Group string
	//关联加载
	Associations []string
	//配置Override
	Selects []string
	Omits   []string
}

func NewFactory(argus ...interface{}) *Factory {
	f := new(Factory).SetConfig(argus...)
	return f
}

func (f *Factory) SetConfig(argus ...interface{}) *Factory {
	for _, arg := range argus {
		tp := reflect.TypeOf(arg)
		switch tp.String() {
		case "context.Context":
			f.Ctx = arg.(context.Context)
		case "[]string":
			f.Associations = arg.([]string)
		case "string":
			f.Group = arg.(string)
		case "bool":
			f.IsEventOn = arg.(bool)
		case "*gorm.DB":
			f.Db = arg.(*gorm.DB)
		case "*orm.EzModelConfig":
			f.ModelConfig = arg.(*EzModelConfig)
		default:
		}
	}
	return f
}
func (this *Factory) DB() *gorm.DB {
	if this.Db == nil {
		this.Db = global.DB
	}
	return this.Db
}
func (this *Factory) SetDb(tx *gorm.DB) {
	this.Db = tx
}
func (this *Factory) SetCtx(ctx context.Context) {
	this.Ctx = ctx
}
func (this *Factory) GetCtx() context.Context {
	return this.Ctx
}
func (this *Factory) SetAssociations(fields ...string) {
	this.Associations = fields
}
func (this *Factory) AddAssociations(fields ...string) {
	this.Associations = ez.UnionStrings(this.Associations, fields)
}

func (this *Factory) Create(md interface{}) error {
	this.TriggerBeforeCreate(md)
	selects := this.ModelConfig.GetCurdConfig(this.Group).Select
	if this.Selects != nil {
		selects = ez.SnakeStrings(this.Selects)
	}
	omits := this.ModelConfig.GetCurdConfig(this.Group).Omit
	if this.Omits != nil {
		omits = ez.SnakeStrings(this.Omits)
	}
	tx := this.DB().Select(selects).Omit(omits...).Create(md)
	this.TriggerAfterCreate(md)
	return tx.Error
}
func (this *Factory) Update(md interface{}) error {
	e := this.FillM2O(md)
	if errs.Try(e) {
		return e
	}
	this.TriggerBeforeUpdate(md)
	selects := this.ModelConfig.GetCurdConfig(this.Group).Select
	if this.Selects != nil {
		selects = ez.SnakeStrings(this.Selects)
	}
	omits := this.ModelConfig.GetCurdConfig(this.Group).Omit
	if this.Omits != nil {
		omits = ez.SnakeStrings(this.Omits)
	}
	tx := this.DB().Select(selects).Omit(omits...).Save(md)
	this.TriggerAfterUpdate(md)
	return tx.Error
}
func (this Factory) FillM2M(md interface{}) error {
	tp := reflect.TypeOf(md).Elem()
	va := reflect.ValueOf(md)
	relM2M := this.GetRelateM2M(md)
	for _, fieldName := range relM2M {
		idsFieldName := fmt.Sprintf("%sIds", fieldName)
		if !va.Elem().FieldByName(idsFieldName).IsValid() {
			continue
		}
		//清除原有的关系
		e := this.DB().Model(md).Association(fieldName).Clear()
		if errs.Try(e) {
			return e
		}
		if va.Elem().FieldByName(idsFieldName).IsNil() {
			continue
		}
		if !va.Elem().FieldByName(idsFieldName).IsNil() {
			fieldTye, _ := tp.FieldByName(fieldName)
			subType := fieldTye.Type.Elem().Elem()
			newVa := reflect.MakeSlice(fieldTye.Type, 0, 0)
			toScan := va.Elem().FieldByName(idsFieldName)
			for i := 0; i < toScan.Len(); i++ {
				toAdd := reflect.New(subType)
				toAdd.Elem().FieldByName("Id").SetInt(toScan.Index(i).Int())
				newVa = reflect.Append(newVa, toAdd)
			}
			va.Elem().FieldByName(fieldName).Set(newVa)
		}
	}
	tx := this.DB().Save(md)
	return tx.Error
}
func (this Factory) FillM2O(md interface{}) error {
	tp := reflect.TypeOf(md).Elem()
	va := reflect.ValueOf(md)
	relM2O := this.GetRelateM2O(md)
	for _, fieldName := range relM2O {
		if !va.Elem().FieldByName(fieldName).IsValid() {
			continue
		}
		fieldTye, _ := tp.FieldByName(fieldName)
		va.Elem().FieldByName(fieldName).Set(reflect.Zero(fieldTye.Type))
	}
	return nil
}

func (this Factory) FillM2MId(md interface{}) error {
	va := reflect.ValueOf(md)
	relM2M := this.GetRelateM2M(md)
	for _, fieldName := range relM2M {
		idsFieldName := fmt.Sprintf("%sIds", fieldName)
		if !va.Elem().FieldByName(fieldName).IsValid() {
			continue
		}
		if !va.Elem().FieldByName(fieldName).IsNil() {
			newVa := make([]int, 0)
			toScan := va.Elem().FieldByName(fieldName)
			for i := 0; i < toScan.Len(); i++ {
				vInt64 := toScan.Index(i).Elem().FieldByName("Id").Int()
				newVa = append(newVa, int(vInt64))
			}
			toStet := reflect.ValueOf(newVa)
			va.Elem().FieldByName(idsFieldName).Set(toStet)
		}
	}
	tx := this.DB().Save(md)
	return tx.Error
}

func (this *Factory) Delete(md interface{}) error {
	this.TriggerDelete(md)
	tx := this.DB().Delete(md)
	return tx.Error
}
func (this *Factory) CreateFromObject(jsonData interface{}, md interface{}) error {
	tableByte, err := json.Marshal(jsonData)
	if err != nil {
		return err
	}
	return json.Unmarshal(tableByte, md)
}
func (this *Factory) CreateFromString(jsonString string, md interface{}) error {
	return json.Unmarshal([]byte(jsonString), md)
}
func (this *Factory) ToString() string {
	ret, _ := json.Marshal(this)
	return string(ret)
}

func (this *Factory) FindBy(md interface{}, where map[string]interface{}, order []string, page int, Limit int) error {
	qb := NewQueryBuilder(this.DB(), this.ModelConfig)
	qb.Prepare(where, order, page, Limit)
	qb.Tx.Distinct(fmt.Sprintf("%s.id", this.ModelConfig.GetTableName())).Select(this.ModelConfig.GetCurdConfig(this.Group).Select).
		Omit(this.ModelConfig.GetCurdConfig(this.Group).Omit...)
	for _, association := range this.GetRelate4List(md) {
		qb.Tx.Preload(association)
	}
	tx := qb.Tx.Find(md)
	return tx.Error
}
func (this *Factory) Count(where map[string]interface{}) (int64, error) {
	qb := NewQueryBuilder(this.DB(), this.ModelConfig)
	qb.Prepare(where, nil, 0, 0)
	var count int64 = 0
	tx := qb.Tx.Distinct(fmt.Sprintf("%s.id", this.ModelConfig.GetTableName())).Count(&count)
	return count, tx.Error
}
func (this *Factory) FindOneBy(md interface{}, where map[string]interface{}, order []string) error {
	qb := NewQueryBuilder(this.DB(), this.ModelConfig)
	qb.Prepare(where, order, 1, 1)
	qb.Tx.Select(this.ModelConfig.GetCurdConfig(this.Group).Select).
		Omit(this.ModelConfig.GetCurdConfig(this.Group).Omit...)
	for _, association := range this.GetRelate4Select(md) {
		qb.Tx.Preload(association)
	}
	tx := qb.Tx.First(md)
	return tx.Error
}
func (this *Factory) Find(md interface{}, id int) error {
	return this.FindOneBy(md, ez.M{"id": id}, []string{"-id"})
}

func (this *Factory) GetRelateFields(md interface{}) (fields []string) {
	fields = make([]string, 0)
	tp := reflect.TypeOf(md)
	//判断指针并取出
	//if tp.Kind().String() == "ptr" {
	//	tp = tp.Elem()
	//}
	//ez.Debug(tp.String())
	switch {
	case tp.Kind().String() == "ptr":
		tp = tp.Elem()
	case tp.Kind().String() == "struct":
		va, ok := md.(reflect.Value)
		if ok {
			va = va.Elem()
			tp = va.Type()
		}
	default:
		ez.Debug(tp.Elem().String())
	}
	if tp.String()[:3] == "[]*" {
		tp = tp.Elem().Elem()
	}
	for i := 0; i < tp.NumField(); i++ {
		fieldType := tp.Field(i).Type.String()
		fieldName := tp.Field(i).Name
		//fmt.Printf("type is %s,name is %s\n", fieldType, fieldName)
		switch {
		case strings.HasPrefix(fieldType, "*") && !strings.HasSuffix(fieldType, "time.Time"):
			fields = append(fields, fieldName)
		case strings.HasPrefix(fieldType, "[]*"):
			fields = append(fields, fieldName)
		}
	}
	return
}

func (this *Factory) GetRelateM2O(md interface{}) (fields []string) {
	fields = make([]string, 0)
	tp := reflect.TypeOf(md)
	switch {
	case tp.Kind().String() == "ptr":
		tp = tp.Elem()
	case tp.Kind().String() == "struct":
		va, ok := md.(reflect.Value)
		if ok {
			va = va.Elem()
			tp = va.Type()
		}
	}
	for i := 0; i < tp.NumField(); i++ {
		fieldName := tp.Field(i).Name
		if tp.Field(i).Tag.Get("flag") == "M2O" {
			fields = append(fields, fieldName)
		}
	}
	return
}

func (this *Factory) GetRelateM2M(md interface{}) (fields []string) {
	fields = make([]string, 0)
	tp := reflect.TypeOf(md)

	switch {
	case tp.Kind().String() == "ptr":
		tp = tp.Elem()
	case tp.Kind().String() == "struct":
		va, ok := md.(reflect.Value)
		if ok {
			va = va.Elem()
			tp = va.Type()
		}
	}
	for i := 0; i < tp.NumField(); i++ {
		fieldName := tp.Field(i).Name
		if tp.Field(i).Tag.Get("flag") == "M2M" {
			fields = append(fields, fieldName)
		}
	}
	return
}

func (this *Factory) GetRelate4Select(md interface{}) (fields []string) {
	if this.Associations != nil {
		return this.Associations
	}
	if len(this.ModelConfig.GetCurdConfig(this.Group).Relations) > 0 {
		return this.ModelConfig.GetCurdConfig(this.Group).Relations
	}
	return this.GetRelateFields(md)
	//selects := this.ModelConfig.GetCurdConfig(this.Group).Select
	//omits := this.ModelConfig.GetCurdConfig(this.Group).Omit
	//tmp := ez.IntersectionStrings(hasRelations, ez.UcfirstArr(selects))
	//return ez.DifferenceStrings(tmp, ez.UcfirstArr(omits))
}
func (this *Factory) GetRelate4Scan(md interface{}) (fields []string) {
	if this.Associations != nil {
		return this.Associations
	}
	hasRelations := this.GetRelateFields(md)
	selects := this.ModelConfig.GetCurdConfig(this.Group).Select
	omits := this.ModelConfig.GetCurdConfig(this.Group).Omit
	tmp := ez.IntersectionStrings(hasRelations, ez.UcfirstArr(selects))
	return ez.DifferenceStrings(tmp, ez.UcfirstArr(omits))
}
func (this *Factory) GetRelate4List(md interface{}) (fields []string) {
	if this.Associations != nil {
		return this.Associations
	}
	hasRelations := this.GetRelateFields(md)
	selects := this.ModelConfig.GetCurdConfig(this.Group).Select
	omits := this.ModelConfig.GetCurdConfig(this.Group).Omit
	tmp := ez.IntersectionStrings(hasRelations, ez.UcfirstArr(selects))
	return ez.DifferenceStrings(tmp, ez.UcfirstArr(omits))
}

func (this *Factory) Get4Edit(md interface{}) (err error) {
	tp := reflect.TypeOf(md)
	va := reflect.ValueOf(md)
	//判断指针并取出
	if tp.Kind().String() == "ptr" {
		tp = tp.Elem()
		va = va.Elem()
	}
	id := va.FieldByName("Id").Int()
	rels := this.GetRelateM2M(md)
	this.SetAssociations(rels...)
	return this.Find(md, int(id))
}

func (this *Factory) Scan(md interface{}) (err error) {

	return
}
